package com.wmx.jdbc_template_app.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.wmx.jdbc_template_app.utils.ResultCode;
import com.wmx.jdbc_template_app.utils.ResultData;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.swing.filechooser.FileSystemView;
import java.io.*;
import java.sql.Date;
import java.sql.*;
import java.util.*;

/**
 * 原生 JDBC 测试 Oracle 数据类型
 * 1、Long 大文本类型
 * 2、CLOB 超大文本类型
 * 3、BLOB 二进制对象
 * 4、NVARCHAR2
 *
 * <p>
 * 1、辅助脚本：src\main\resources\data\sql\oracle\dept_2.sql
 * 2、被存储的大文本文件内容，普通的 varchar2 类型是存储不下的（被插入内容长度已大于4000字节）。
 * * src\main\resources\data\settings-大文本内容测试.xml
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2023/12/21 16:44
 */
@RestController
@SuppressWarnings("Duplicates")
public class OracleDataType {

    private static final Logger log = LoggerFactory.getLogger(OracleDataType.class);

    /**
     * Long 大文本类型 - 测试 - 原生 JDBC
     * 1、操作(增删改查)与普通的 VARCHAR2 类型完全一样.
     * <p>
     * http://localhost:8080/oracle/type/long1
     * <p>
     */
    @GetMapping("oracle/type/long1")
    public ResultData long1() {
        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet resultSet = null;
        InputStream inputStream = null;
        try {
            // settings-大文本内容测试.xml
            inputStream = OracleDataType.class.getClassLoader().getResourceAsStream("data/settings-大文本内容测试.xml");
            String longText = IoUtil.readUtf8(inputStream);

            String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
            // 注册驱动
            Class.forName("oracle.jdbc.driver.OracleDriver");

            // 获取连接
            Properties props = new Properties();
            props.setProperty("user", "scott");
            props.setProperty("password", "scott");
            // 设置可以获取表、字段的remarks描述信息
            props.setProperty("remarks", "true");
            props.setProperty("useInformationSchema", "true");
            conn = DriverManager.getConnection(url, props);

            // 关闭事务自动提交，改为手动提交
            conn.setAutoCommit(false);

            // 获取预编译SQL语句对象
            String deptNo = IdUtil.fastSimpleUUID();
            String insertSql = "insert into dept_2 (deptno, dname, update_time, loc_long) values (?, ?, ?, ?) ";
            stmt = conn.prepareStatement(insertSql);
            // 直接 stmt.setObject(xx) 赋值也是可以的.
            stmt.setString(1, deptNo);
            stmt.setString(2, "AI研发中心");
            stmt.setObject(3, new Date(DateUtil.date().getTime()));
            stmt.setString(4, longText);

            // 4、执行SQL操作，查询操作会返回结果集(相当于一个游标)
            stmt.executeUpdate();

            // 获取结果集
            String querySql = "select deptno, dname, update_time, loc_long from dept_2 where deptno = ?";
            stmt = conn.prepareStatement(querySql);
            stmt.setObject(1, deptNo);
            resultSet = stmt.executeQuery();

            List<Map<String, Object>> mapList = new ArrayList<>();
            // 结果集元数据对象，可以用于获取表的列信息，即使没有查询到值，列信息同样会有
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int columnCount = resultSetMetaData.getColumnCount();
            while (resultSet.next()) {
                Map<String, Object> rowData = new LinkedHashMap<>(columnCount);
                for (int i = 1; i <= columnCount; i++) {
                    Object object = resultSet.getObject(i);
                    // 将时间格式化为"yyyy-MM-dd HH:mm:ss"
                    if (object != null && object instanceof java.util.Date) {
                        object = DateUtil.formatDateTime((java.util.Date) object);
                    }
                    rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                }
                mapList.add(rowData);
            }
            // 提交事务
            conn.commit();
            return ResultData.success(mapList);
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            log.error(ExceptionUtils.getStackTrace(e));
            return ResultData.error(ResultCode.FAIL, e);
        } finally {
            // 关闭数据库连接
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
        }
    }

    /**
     * CLOB 超大文本类型 - 测试1 - 原生 JDBC
     * <p>
     * http://localhost:8080/oracle/type/clob1
     * <p>
     * 1、CLOB、BLOB 类型数据的操作，原生JDBC中，需要使用 PreparedStatement。
     * 2、clob 类型时，比如存储文章，新闻、很长内容的SQL配置等等，文本内容超过4000字节，varchar2无法存储了。
     * * 赋值方式1：拿到需要存储的文本内容后，直接使用 stmt.setString、stmt.setObject 赋值就行，和操作 varchar2 类型一样。
     * * 赋值方式2：使用字符流赋值：
     * * void setCharacterStream(int parameterIndex,java.io.Reader reader,int length)
     * * void setCharacterStream(int parameterIndex, java.io.Reader reader)
     * * void setClob(int parameterIndex, java.io.Reader reader)
     * * void setClob(int parameterIndex, java.io.Reader reader, long length)
     * * void setClob(int parameterIndex, java.sql.Clob x)
     * * 读取时：
     * * * java.io.Reader characterStream = resultSet.getCharacterStream(i); 进行字符流读取。
     * * * java.sql.Clob resultSet.getClob(int columnIndex)
     * 3、CLOB 使用字符流操作，Blob 使用字节流操作。
     *
     * @throws SQLException
     */
    @GetMapping("oracle/type/clob1")
    public ResultData clob1() {
        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet resultSet = null;
        InputStream inputStream = null;
        try {
            inputStream = OracleDataType.class.getClassLoader().getResourceAsStream("data/settings-大文本内容测试.xml");
            String clobText = IoUtil.readUtf8(inputStream);

            String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
            // 注册驱动
            Class.forName("oracle.jdbc.driver.OracleDriver");

            // 获取连接
            Properties props = new Properties();
            props.setProperty("user", "scott");
            props.setProperty("password", "scott");
            // 设置可以获取表、字段的remarks描述信息
            props.setProperty("remarks", "true");
            props.setProperty("useInformationSchema", "true");
            conn = DriverManager.getConnection(url, props);

            // 关闭事务自动提交，改为手动提交
            conn.setAutoCommit(false);

            // 获取预编译SQL语句对象
            String deptNo = IdUtil.fastSimpleUUID();
            String insertSql = "insert into dept_2 (deptno, dname, update_time, remark_clob) values (?, ?, ?, ?) ";
            stmt = conn.prepareStatement(insertSql);
            // 直接 stmt.setObject(xx) 赋值也是可以的.
            stmt.setString(1, deptNo);
            stmt.setString(2, "大模型研发中心");
            stmt.setObject(3, new Date(DateUtil.date().getTime()));
            stmt.setString(4, clobText);

            // 4、执行SQL操作，查询操作会返回结果集(相当于一个游标)
            stmt.executeUpdate();

            // 获取结果集
            String querySql = "select deptno, dname, update_time, remark_clob from dept_2 where deptno = ?";
            stmt = conn.prepareStatement(querySql);
            stmt.setObject(1, deptNo);
            resultSet = stmt.executeQuery();

            List<Map<String, Object>> mapList = new ArrayList<>();
            // 结果集元数据对象，可以用于获取表的列信息，即使没有查询到值，列信息同样会有
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int columnCount = resultSetMetaData.getColumnCount();
            while (resultSet.next()) {
                Map<String, Object> rowData = new LinkedHashMap<>(columnCount);
                for (int i = 1; i <= columnCount; i++) {
                    Object object = resultSet.getObject(i);
                    if (object == null) {
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), null);
                        continue;
                    }
                    // 将时间格式化为"yyyy-MM-dd HH:mm:ss"
                    if (object instanceof java.util.Date) {
                        object = DateUtil.formatDateTime((java.util.Date) object);
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                        continue;
                    }
                    // CLOB 类型需要使用字符流读取
                    if ("CLOB".equalsIgnoreCase(resultSetMetaData.getColumnTypeName(i))) {
                        StringBuffer sb = new StringBuffer();
                        // 从clob中得到流
                        Reader reader = resultSet.getCharacterStream(i);
                        BufferedReader bufferedReader = new BufferedReader(reader);
                        // 执行循环将字符串全部取出付值给StringBuffer由StringBuffer转成STRING
                        String readLine = bufferedReader.readLine();
                        while (readLine != null) {
                            sb.append(readLine);
                            readLine = bufferedReader.readLine();
                        }
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), sb.toString());
                        continue;
                    }
                    rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                }
                mapList.add(rowData);
            }
            // 提交事务
            conn.commit();
            return ResultData.success(mapList);
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            log.error(ExceptionUtils.getStackTrace(e));
            return ResultData.error(ResultCode.FAIL, e);
        } finally {
            // 关闭数据库连接
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
        }
    }

    /**
     * CLOB 超大文本类型 - 测试2 - 原生 JDBC
     * <p>
     * http://localhost:8080/oracle/type/clob2
     * <p>
     * 1、CLOB、BLOB 类型数据的操作，原生JDBC中，需要使用 PreparedStatement。
     * 2、clob 类型时，比如存储文章，新闻、很长内容的SQL配置等等，文本内容超过4000字节，varchar2无法存储了。
     * * 赋值方式1：拿到需要存储的文本内容后，直接使用 stmt.setString、stmt.setObject 赋值就行，和操作 varchar2 类型一样。
     * * 赋值方式2：使用字符流赋值：
     * * void setCharacterStream(int parameterIndex,java.io.Reader reader,int length)
     * * void setCharacterStream(int parameterIndex,java.io.Reader reader)
     * * void setClob(int parameterIndex, java.io.Reader reader)
     * * void setClob(int parameterIndex, java.io.Reader reader, long length)
     * * void setClob(int parameterIndex, java.sql.Clob x)
     * * 读取时：
     * * * java.io.Reader characterStream = resultSet.getCharacterStream(i); 进行字符流读取。
     * * * java.sql.Clob resultSet.getClob(int columnIndex)
     * 3、CLOB 使用字符流操作，Blob 使用字节流操作。
     * <p>
     *
     * @throws SQLException
     */
    @GetMapping("oracle/type/clob2")
    public ResultData clob2() {
        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet resultSet = null;
        InputStream inputStream = null;
        try {
            inputStream = OracleDataType.class.getClassLoader().getResourceAsStream("data/settings-大文本内容测试.xml");
            BufferedReader buffReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

            String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
            // 注册驱动
            Class.forName("oracle.jdbc.driver.OracleDriver");

            // 获取连接
            Properties props = new Properties();
            props.setProperty("user", "scott");
            props.setProperty("password", "scott");
            // 设置可以获取表、字段的remarks描述信息
            props.setProperty("remarks", "true");
            props.setProperty("useInformationSchema", "true");
            conn = DriverManager.getConnection(url, props);

            // 关闭事务自动提交，改为手动提交
            conn.setAutoCommit(false);

            // 获取预编译SQL语句对象
            String deptNo = IdUtil.fastSimpleUUID();
            String insertSql = "insert into dept_2 (deptno, dname, update_time, remark_clob) values (?, ?, ?, ?) ";
            stmt = conn.prepareStatement(insertSql);
            stmt.setObject(1, deptNo);
            stmt.setObject(2, "机器人研发中心");
            stmt.setObject(3, new Date(DateUtil.date().getTime()));
            // 如果是字符串，则可以直接 stmt.setString、stmt.setObject 赋值也是可以的。只需要提前读取了文件中的内容即可。
            // 如果是文件流，则可以使用读字符流的方式.
            stmt.setCharacterStream(4, buffReader);

            // 4、执行SQL操作，查询操作会返回结果集(相当于一个游标)
            stmt.executeUpdate();

            // 获取结果集
            String querySql = "select deptno, dname, update_time, remark_clob from dept_2 where deptno = ?";
            stmt = conn.prepareStatement(querySql);
            stmt.setObject(1, deptNo);
            resultSet = stmt.executeQuery();

            List<Map<String, Object>> mapList = new ArrayList<>();
            // 结果集元数据对象，可以用于获取表的列信息，即使没有查询到值，列信息同样会有
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int columnCount = resultSetMetaData.getColumnCount();
            while (resultSet.next()) {
                Map<String, Object> rowData = new LinkedHashMap<>(columnCount);
                for (int i = 1; i <= columnCount; i++) {
                    Object object = resultSet.getObject(i);
                    if (object == null) {
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), null);
                        continue;
                    }
                    // 将时间格式化为"yyyy-MM-dd HH:mm:ss"
                    if (object instanceof java.util.Date) {
                        object = DateUtil.formatDateTime((java.util.Date) object);
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                        continue;
                    }
                    // 如果是存储的字符串内容，则可以直接获取，而不用再通过字符流
                    if (object != null && object instanceof Clob) {
                        Clob clob = (Clob) object;
                        String content = clob.getSubString(1L, (int) clob.length());
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), content);
                        continue;
                    }
                    rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                }
                mapList.add(rowData);
            }
            // 提交事务
            conn.commit();
            return ResultData.success(mapList);
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            log.error(ExceptionUtils.getStackTrace(e));
            return ResultData.error(ResultCode.FAIL, e);
        } finally {
            // 关闭数据库连接
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
        }
    }

    /**
     * BLOB 二进制对象类型 - 测试1 - 原生 JDBC
     * <p>
     * http://localhost:8080/oracle/type/blob1
     * <p>
     * 1、CLOB、BLOB 类型数据的操作，原生JDBC中，需要使用 PreparedStatement。
     * 2、通常很少会把 视频、音频、图片，word、excel、ppt等等文件存储在数据库，一般还是用来存储大文本字符串内容多一些。
     * * Blob 赋值：
     * * * void setBlob(int parameterIndex, Blob x)
     * * * void setBlob(int parameterIndex, InputStream inputStream)
     * * * void setBlob(int parameterIndex, InputStream inputStream, long length)
     * * * void setBinaryStream(int parameterIndex, java.io.InputStream x)
     * * * void setBinaryStream(int parameterIndex, java.io.InputStream x, long length)
     * * Blob 取值：
     * * * java.io.InputStream getBinaryStream(int columnIndex)
     * * * Blob getBlob(int columnIndex)
     * 3、CLOB 使用字符流操作，Blob 使用字节流操作。
     *
     * @return
     */
    @GetMapping("oracle/type/blob1")
    public ResultData blob1() {
        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet resultSet = null;
        InputStream inputStream = null;
        try {
            inputStream = OracleDataType.class.getClassLoader().getResourceAsStream("data/settings-大文本内容测试.xml");

            String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
            // 注册驱动
            Class.forName("oracle.jdbc.driver.OracleDriver");

            // 获取连接
            Properties props = new Properties();
            props.setProperty("user", "scott");
            props.setProperty("password", "scott");
            // 设置可以获取表、字段的remarks描述信息
            props.setProperty("remarks", "true");
            props.setProperty("useInformationSchema", "true");
            conn = DriverManager.getConnection(url, props);

            // 关闭事务自动提交，改为手动提交
            conn.setAutoCommit(false);

            // 3、获取预编译SQL语句对象
            String deptNo = IdUtil.fastSimpleUUID();
            String insertSql = "insert into dept_2 (deptno, dname, update_time, ico_blob) values (?, ?, ?, ?) ";
            stmt = conn.prepareStatement(insertSql);
            stmt.setObject(1, deptNo);
            stmt.setString(2, "自媒体研发中心");
            stmt.setObject(3, new Date(DateUtil.date().getTime()));
            stmt.setBlob(4, inputStream);

            // 如果是大文本内容，则也可以转成字节流后存储。
            // ByteArrayInputStream arrayInputStream = IoUtil.toStream("xxxx", Charset.forName("UTF-8"));
            // stmt.setBinaryStream(4, arrayInputStream);

            // 4、执行SQL操作，查询操作会返回结果集(相当于一个游标)
            stmt.executeUpdate();

            // 5、获取结果集
            String querySql = "select deptno, dname, update_time, ico_blob from dept_2 where deptno = ?";
            stmt = conn.prepareStatement(querySql);
            stmt.setObject(1, deptNo);
            resultSet = stmt.executeQuery();

            List<Map<String, Object>> mapList = new ArrayList<>();
            // 结果集元数据对象，可以用于获取表的列信息，即使没有查询到值，列信息同样会有
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int columnCount = resultSetMetaData.getColumnCount();
            while (resultSet.next()) {
                Map<String, Object> rowData = new LinkedHashMap<>(columnCount);
                for (int i = 1; i <= columnCount; i++) {
                    Object object = resultSet.getObject(i);
                    if (object == null) {
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), null);
                        continue;
                    }
                    // 将时间格式化为"yyyy-MM-dd HH:mm:ss"
                    if (object instanceof java.util.Date) {
                        object = DateUtil.formatDateTime((java.util.Date) object);
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                        continue;
                    }
                    // BLOB 类型需要使用字节流读取 - 文本文件时，读取文件内容。
                    // if ("BLOB".equalsIgnoreCase(resultSetMetaData.getColumnTypeName(i))) {
                    //     String readUtf8 = "";
                    //     InputStream binaryStream = resultSet.getBinaryStream(i);
                    //     if (binaryStream != null) {
                    //         readUtf8 = IoUtil.readUtf8(binaryStream);
                    //     }
                    //     rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), readUtf8);
                    //     continue;
                    // }

                    // 如果是读取原生字节数据，则可以使用下面这种更简单的读取方式.
                    if (object != null && object instanceof Blob) {
                        Blob blob = (Blob) object;
                        byte[] byteData = blob.getBytes(1L, (int) blob.length());
                        System.out.println(new String(byteData, "UTF-8"));
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), byteData);
                        continue;
                    }
                    rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                }
                mapList.add(rowData);
            }
            // 提交事务
            conn.commit();
            return ResultData.success(mapList);
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            log.error(ExceptionUtils.getStackTrace(e));
            return ResultData.error(ResultCode.FAIL, e);
        } finally {
            // 关闭数据库连接
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
        }
    }

    /**
     * BLOB 二进制对象类型 - 测试2 - 原生 JDBC
     * <p>
     * http://localhost:8080/oracle/type/blob2
     * <p>
     * 1、CLOB、BLOB 类型数据的操作，原生JDBC中，需要使用 PreparedStatement。
     * 2、通常很少会把 视频、音频、图片，word、excel、ppt等等文件存储在数据库，一般还是用来存储大文本字符串内容多一些。
     * * Blob 赋值：
     * * * void setBlob(int parameterIndex, Blob x)
     * * * void setBlob(int parameterIndex, InputStream inputStream)
     * * * void setBlob(int parameterIndex, InputStream inputStream, long length)
     * * * void setBinaryStream(int parameterIndex, java.io.InputStream x)
     * * * void setBinaryStream(int parameterIndex, java.io.InputStream x, long length)
     * * Blob 取值：
     * * * java.io.InputStream getBinaryStream(int columnIndex)
     * * * Blob getBlob(int columnIndex)
     * 3、CLOB 使用字符流操作，Blob 使用字节流操作。
     *
     * @return
     */
    @GetMapping("oracle/type/blob2")
    public ResultData blob2() {
        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet resultSet = null;
        InputStream inputStream = null;
        try {
            String path = "data/images/西方美女.jpg";
            inputStream = OracleDataType.class.getClassLoader().getResourceAsStream(path);

            String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
            // 注册驱动
            Class.forName("oracle.jdbc.driver.OracleDriver");

            // 获取连接
            Properties props = new Properties();
            props.setProperty("user", "scott");
            props.setProperty("password", "scott");
            // 设置可以获取表、字段的remarks描述信息
            props.setProperty("remarks", "true");
            props.setProperty("useInformationSchema", "true");
            conn = DriverManager.getConnection(url, props);

            // 关闭事务自动提交，改为手动提交
            conn.setAutoCommit(false);

            // 3、获取预编译SQL语句对象
            String deptNo = IdUtil.fastSimpleUUID();
            String insertSql = "insert into dept_2 (deptno, dname, update_time, ico_blob) values (?, ?, ?, ?) ";
            stmt = conn.prepareStatement(insertSql);
            stmt.setObject(1, deptNo);
            stmt.setString(2, "自媒体研发中心");
            stmt.setObject(3, new Date(DateUtil.date().getTime()));
            // stmt.setBlob(4, inputStream);
            stmt.setBinaryStream(4, inputStream);

            // 4、执行SQL操作，查询操作会返回结果集(相当于一个游标)
            stmt.executeUpdate();

            // 5、获取结果集
            String querySql = "select deptno, dname, update_time, ico_blob from dept_2 where deptno = ?";
            stmt = conn.prepareStatement(querySql);
            stmt.setObject(1, deptNo);
            resultSet = stmt.executeQuery();

            List<Map<String, Object>> mapList = new ArrayList<>();
            // 结果集元数据对象，可以用于获取表的列信息，即使没有查询到值，列信息同样会有
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int columnCount = resultSetMetaData.getColumnCount();
            while (resultSet.next()) {
                Map<String, Object> rowData = new LinkedHashMap<>(columnCount);
                for (int i = 1; i <= columnCount; i++) {
                    Object object = resultSet.getObject(i);
                    if (object == null) {
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), null);
                        continue;
                    }
                    // 将时间格式化为"yyyy-MM-dd HH:mm:ss"
                    if (object instanceof java.util.Date) {
                        object = DateUtil.formatDateTime((java.util.Date) object);
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                        continue;
                    }
                    // BLOB 类型需要使用字节流读取 - 因为是图片，所以这里简单的演示一下，将它保存到周末上。
                    if ("BLOB".equalsIgnoreCase(resultSetMetaData.getColumnTypeName(i))) {
                        InputStream binaryStream = resultSet.getBinaryStream(i);
                        if (binaryStream != null) {
                            File homeDirectory = FileSystemView.getFileSystemView().getHomeDirectory();
                            File file = new File(homeDirectory, path);
                            FileUtil.writeFromStream(binaryStream, file);
                            binaryStream.close();
                            rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), file.getAbsolutePath());
                        }
                        continue;
                    }
                    rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                }
                mapList.add(rowData);
            }
            // 提交事务
            conn.commit();
            return ResultData.success(mapList);
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            log.error(ExceptionUtils.getStackTrace(e));
            return ResultData.error(ResultCode.FAIL, e);
        } finally {
            // 关闭数据库连接
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
        }
    }

    /**
     * nvarchar2 类型 - 测试1 - 原生 JDBC
     * http://localhost:8080/oracle/type/nvarchar2
     * <p>
     * 1、NVARCHAR2 是可以存储 Unicode 字符的 Unicode 数据类型，所以可以用来存储生僻字，比如 '䶮' 字直接存储在 varchar2 中是乱码的。
     * 2、有生僻字时：
     * * 赋值：
     * * * void setNString(int parameterIndex, String value)
     * * 取值：
     * * * resultSet.getNString
     * * * resultSet.getString
     * * * resultSet.getObject
     *
     * @return
     */
    @GetMapping("oracle/type/nvarchar2")
    public ResultData nvarchar2() {
        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet resultSet = null;
        InputStream inputStream = null;
        try {
            String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
            // 注册驱动
            Class.forName("oracle.jdbc.driver.OracleDriver");

            // 获取连接
            Properties props = new Properties();
            props.setProperty("user", "scott");
            props.setProperty("password", "scott");
            // 设置可以获取表、字段的remarks描述信息
            props.setProperty("remarks", "true");
            props.setProperty("useInformationSchema", "true");
            conn = DriverManager.getConnection(url, props);

            // 关闭事务自动提交，改为手动提交
            conn.setAutoCommit(false);

            // 3、获取预编译SQL语句对象
            String deptNo = IdUtil.fastSimpleUUID();
            // 方式1：直接特殊拼接'N'，不使用占位符
            // String insertSql = "insert into dept_2 (deptno, dname, update_time, tip_nvarchar2) values (?, ?, ?, N'䶮平陵') ";
            // 方式2：使用 setNString 进行赋值
            String insertSql = "insert into dept_2 (deptno, dname, update_time, tip_nvarchar2) values (?, ?, ?, ?) ";
            stmt = conn.prepareStatement(insertSql);
            stmt.setObject(1, deptNo);
            stmt.setString(2, "直播带货研发中心");
            stmt.setObject(3, new Date(DateUtil.date().getTime()));
            stmt.setNString(4, "䶮平陵王");

            // 4、执行SQL操作，查询操作会返回结果集(相当于一个游标)
            stmt.executeUpdate();

            // 5、获取结果集
            String querySql = "select deptno, dname, update_time, tip_nvarchar2 from dept_2 where deptno = ?";
            stmt = conn.prepareStatement(querySql);
            stmt.setObject(1, deptNo);
            resultSet = stmt.executeQuery();

            List<Map<String, Object>> mapList = new ArrayList<>();
            // 结果集元数据对象，可以用于获取表的列信息，即使没有查询到值，列信息同样会有
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int columnCount = resultSetMetaData.getColumnCount();
            while (resultSet.next()) {
                Map<String, Object> rowData = new LinkedHashMap<>(columnCount);
                for (int i = 1; i <= columnCount; i++) {
                    Object object = resultSet.getObject(i);
                    if (object == null) {
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), null);
                        continue;
                    }
                    // 将时间格式化为"yyyy-MM-dd HH:mm:ss"
                    if (object instanceof java.util.Date) {
                        object = DateUtil.formatDateTime((java.util.Date) object);
                        rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                        continue;
                    }
                    rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
                }
                mapList.add(rowData);
            }
            // 提交事务
            conn.commit();
            return ResultData.success(mapList);
        } catch (Exception e) {
            if (conn != null) {
                // 回滚事务
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            log.error(ExceptionUtils.getStackTrace(e));
            return ResultData.error(ResultCode.FAIL, e);
        } finally {
            // 关闭数据库连接
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
        }
    }


}
